﻿namespace MetaSharp.Transformation
{
    public class MemoTable
    {
        public static readonly MemoTable Empty = new MemoTable(
            Map<MemoKey, Memo>.Empty,
            Set<object, MemoKey>.Empty,
            null);

        private Map<MemoKey, Memo> _memos;
        private Set<object, MemoKey> _index;
        private MemoTable _previous;

        private MemoTable(
            Map<MemoKey, Memo> memos,
            Set<object, MemoKey> index,
            MemoTable previous)
        {
            _memos = memos;
            _index = index;
            _previous = previous;
        }

        internal IImmutableArray<Memo> NewerThan(MemoTable table)
        {
            return this._memos.NewerThan(table._memos);
        }

        public MemoTable Step()
        {
            return new MemoTable(
                Map<MemoKey, Memo>.Empty,
                Set<object, MemoKey>.Empty,
                this);
        }

        public bool TryGet(MemoKey key, out Memo memo)
        {
            return _memos.TryGet(key, out memo);
        }

        public MemoTable Add(MemoKey key, Memo memo)
        {
            var memos = _memos.Add(key, memo);
            var index = _index;
            if (memo.Match.Matched && memo.Match.Value != null)
                index = _index.Add(memo.Match.Value, key);

            return new MemoTable(memos, index, _previous);
        }

        public IImmutableArray<RuleMetadata> GetMetadata(object value)
        {
            var metadata = new ArrayList<RuleMetadata>();
            GetMetadata(value, metadata);
            return metadata;
        }

        private void GetMetadata(object value, ArrayList<RuleMetadata> metadata)
        {
            foreach (var key in _index[value])
            {
                var memo = _memos[key];
                GetMetadata(memo, metadata);

                foreach (var reference in memo.References)
                    GetMetadata(reference, metadata);

                if (_previous != null)
                {
                    if (!key.Stream.IsEmpty)
                    {
                        var next = key.Stream.Next();
                        _previous.GetMetadata(next.Current, metadata);
                    }
                }
            }
        }

        private void GetMetadata(Memo memo, ArrayList<RuleMetadata> metadata)
        {
            if (memo.Origin != null)
            {
                var atts = memo.Origin.Attributes;
                if (atts.Length > 0)
                {
                    metadata.Add(new RuleMetadata(
                        memo.Match.Value, 
                        memo.Origin,
                        Trace(memo.Match.Value), // TODO: do this lazily
                        atts));
                }
            }
        }

        public SourceSpan Trace(object value)
        {
            var traced = default(SourceSpan);
            foreach (var key in _index[value])
            {
                var memo = _memos[key];
                foreach (var reference in memo.References)
                {
                    var span = Trace(reference);
                    traced += span;
                }

                if (_previous == null)
                {
                    traced += new SourceSpan(
                        memo.Match.Start.Stream,
                        memo.Match.End.Stream);
                }
                else
                {
                    var stream = memo.Match.Start.Stream;
                    while(!stream.IsEmpty && stream != memo.Match.End.Stream)
                    {
                        stream = stream.Next();
                        traced += _previous.Trace(stream.Current);
                    }
                }
            }

            return traced;
        }
    }
}
